package BitCreeekClient;

import BitCreekLib.Host;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Map;
import java.util.ArrayList;
import java.util.Random;

/**
 * Created by Ivan Litsvinenka e Emanuele Ricci
 * Date: 3-ago-2009
 * Time: 16.37.36
 */
public class PeerP2PClientWorker implements Runnable {

    private String torrentName;
    private int requestedPart;
    private Random randomGenerator;
    

    public PeerP2PClientWorker ( String torrentName, int requestedPart ) {
        this.torrentName = torrentName;
        this.requestedPart = requestedPart;
        this.randomGenerator = new Random ();
    }

    private boolean checkCompletedPart ( int index, Part[] parts ) {
        return parts[ index ].getStatus () == PartStatusType.COMPLETE;
    }
    
    private int randomHost( int max ) {
        int r;
        int range = max - 0 + 1;
        r = randomGenerator.nextInt ( range );
        return r;
    }
    
    private Host randomPeer() throws UnknownHostException { 
    	
    	ArrayList<Host> hl  = Client.downloads.get( torrentName ).getConnectedPeers();
    	Host selectedHost;
    	PeerInfo bestPeerInfo;
    	boolean best;
    	
    	do {
    		
    		best = false;
    		
    		int randomIndex = randomHost( hl.size()-1 );
    		selectedHost = hl.get(randomIndex);
    		bestPeerInfo = Client.downloads.get( torrentName ).getSwarm().get(selectedHost);
    		
    		if ( selectedHost == null && checkCompletedPart ( requestedPart, bestPeerInfo.getPeerParts () ) )
    			best = true;
            if ( checkCompletedPart ( requestedPart, bestPeerInfo.getPeerParts () ) && ( bestPeerInfo.getUploaded () > Client.downloads.get( torrentName ).getSwarm ().get ( selectedHost ).getUploaded () ) )
            	best = true;
    		
    		
    	} while ( selectedHost.getAddress().equals( InetAddress.getLocalHost() ) && !best  );
    	
    	
    	
    	
    	return selectedHost;
    }

    public Host bestNextPeerWithPart() {
        Host bestPeer = null;
        PeerInfo bestPeerInfo;
        while ( Client.downloads.get( torrentName ).getConnectedPeers().size() < 1 ) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
        }
        
        try {
			bestPeer = randomPeer();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}
        

        return bestPeer;
    }

    
    public void run() {
    	Host host, currentHost;
    	host = null;
    	currentHost = null;
    	
    	if ( Client.downloads.get ( torrentName ).getProgress () == 100 ) return;
    	
    	P2P_Packet response = null;
    	do {
	    	host = bestNextPeerWithPart();
	    	currentHost = host;
	    	PeerInfo currentPeerInfo = Client.downloads.get ( torrentName ).getSwarm ().get ( host );
	    	
	    	try {
	    		ObjectOutputStream output = new ObjectOutputStream ( currentPeerInfo.getSocket ().getOutputStream () );
	            ObjectInputStream input = new ObjectInputStream ( currentPeerInfo.getSocket ().getInputStream () );
	            P2P_Packet request = new P2P_Packet ( P2P_MessageType.GET_PART, requestedPart );
	            output.writeObject ( request );
	
	
	            response = ( P2P_Packet ) input.readObject ();
	
	            if ( response.getType ().equals ( P2P_MessageType.ERROR ) ) {
	                Client.downloads.get ( torrentName ).getSwarm ().get ( currentHost ).setConnected ( false );
	                Client.downloads.get ( torrentName ).getConnectedPeers ().remove ( currentHost );
	                Client.downloads.get ( torrentName ).currentConnectionsCount--;
	                //continue;
	            } else if ( response.getType ().equals ( P2P_MessageType.ERR_PART_INCOMPLETE ) ) {
	            	Client.downloads.get ( torrentName ).parts[ requestedPart ].setIncomplete ();
	            } else {
	            	
	                Client.downloads.get ( torrentName ).setData ( requestedPart, ( byte[] ) response.getValue () );
	                Client.downloads.get ( torrentName ).parts[ requestedPart ].setComplete ();
	                Client.downloads.get ( torrentName ).possesedParts++;
	
	
	                Client.downloads.get ( torrentName ).getSwarm ().get ( currentHost ).setUploaded ( Client.downloads.get ( torrentName ).getSwarm ().get ( currentHost ).getUploaded () + Client.downloads.get ( torrentName ).getData ( requestedPart ).length );
	                if ( Client.downloads.get ( torrentName ).getSwarm ().get ( currentHost ).getFirstTimeUploaded () == -1 ) {
	                    Client.downloads.get ( torrentName ).getSwarm ().get ( currentHost ).setFirstTimeUploaded ( System.currentTimeMillis () );
	                }
	
	                // Notify all connected peers about new part
	                for ( Host currentNotifyHost : Client.downloads.get ( torrentName ).getConnectedPeers () ) {
	                    PeerInfo currentLoop = Client.downloads.get ( torrentName ).getSwarm ().get ( currentNotifyHost );
	                    if ( currentNotifyHost.getAddress ().equals ( InetAddress.getLocalHost () ) && ( !currentLoop.getSocket ().equals ( currentPeerInfo.getSocket () ) ) ) {
	                        ObjectOutputStream outputNotify = new ObjectOutputStream ( currentLoop.getSocket ().getOutputStream () );
	                        ObjectInputStream inputNotify = new ObjectInputStream ( currentLoop.getSocket ().getInputStream () );
	                        P2P_Packet refreshListPacket = new P2P_Packet ( P2P_MessageType.NEW_PARTS, Client.downloads.get ( torrentName ).parts );
	                        outputNotify.writeObject ( refreshListPacket );
	                        outputNotify.flush ();
	                        outputNotify.reset ();
	                        P2P_Packet responseNotify = ( P2P_Packet ) inputNotify.readObject ();
	                        if ( responseNotify.getType () == P2P_MessageType.ERROR ) Log.add ( "ERROR" );
	                    }
	                }
	            }
	            
	            applicationWindow.c.fireGUIEvent ( new GUIEvent ( Client.downloads.get ( torrentName ) ) );
	            if ( Client.downloads.get ( torrentName ).parts[ requestedPart ].getStatus () != PartStatusType.COMPLETE )
	                Client.downloads.get ( torrentName ).parts[ requestedPart ].setIncomplete ();
	            
	    	} catch ( SocketException e ){
	            Client.downloads.get ( torrentName ).parts[ requestedPart ].setIncomplete ();
	            Client.downloads.get ( torrentName ).getConnectedPeers ().remove ( host );
	            try {
	                Client.downloads.get ( torrentName ).getSwarm ().get ( host ).getSocket ().close ();
	            } catch ( IOException e1 ) {
	                e1.printStackTrace ();
	            }
	            Log.add ( "Host: "+host.toString() + " connection lost, removing from connectedPeers");
	        } catch ( Exception e ) {
	            Client.downloads.get ( torrentName ).parts[ requestedPart ].setIncomplete ();
	
	            e.printStackTrace ();
	        } 
    	} while ( response != null && response.getType ().equals ( P2P_MessageType.ERROR ) );
    	
		return;
    	
    }


}
